﻿using System;
using System.Collections.Generic;
using System.Linq;
using COFE.Core;
using COFE.Core.Lister;
using COFE.Core.Profile;
using NUnit.Framework;
using COFE.Core.Entry;
using COFE.Core.Utils;

namespace COFE.Data
{
    /// <summary>
    /// Enable cache the any entry based property provider.
    /// </summary>
    public abstract class DBPropertyProviderBase : IPropertyProvider
    {
        #region Constructor

        private void init(Entry dbEntry)
        {
            _dbEntry = dbEntry;
            _dbEntryId = dbEntry.EntryId;
            _fullParsePath = COFEPath.FormatFullParseName(dbEntry.PrimaryVolumeId, dbEntry.ParsePath);
        }

        protected DBPropertyProviderBase(Entry dbEntry)
        {
            Assert.IsNotNull(dbEntry);
            init(dbEntry);
        }

        /// <summary>
        /// Assumed the dbEntry already exists.
        /// </summary>
        /// <param name="entryGuid"></param>
        protected DBPropertyProviderBase(Guid entryGuid)
        {
            COFEDBShared.DBOperations.ReadDatabase(
                (context) => COFEDBShared.DBEntryManager.LookupEntry(context, entryGuid),
                (context, dbEntry) =>
                {
                    if (dbEntry == null)
                        throw new KeyNotFoundException(entryGuid.ToString() + " is not found.");
                    init(dbEntry);
                });
        }



        #endregion



        #region Public Methods

        public virtual void Refresh()
        {
            var checkExists = COFEShared.CurrentUserProfile.TryParsePath(_fullParsePath,
                EntryConstructionFlags.DefaultWithoutCache | EntryConstructionFlags.LookupIfParseFailed);
            if (checkExists == null)
                COFEDBShared.DBEntryManager.RemoveEntry(_dbEntryId);
            else
                COFEDBShared.DBEntryManager.ExpireEntry(_dbEntryId, true, true);
        }


        public virtual IEnumerable<object> GetSupportedProperty()
        {
            var returnedProperties = new List<object>();
            Func<object, bool> checkProperty = (prop) =>
                {
                    if (!returnedProperties.Contains(prop))
                    {
                        returnedProperties.Add(prop);
                        return true;
                    }
                    else return false;
                };

            bool isFolder = false;
            object[] readableProperties = new object[] { },
                writableProperties = new object[] { };


            readableProperties = (from intString in _dbEntry.SupportedAttributes.Split(',')
                                  select COFEShared.PropertyDictionary.GetProperty(Int32.Parse(intString))).ToArray();
            writableProperties = (from intString in _dbEntry.WritableAttributes.Split(',')
                                  select COFEShared.PropertyDictionary.GetProperty(Int32.Parse(intString))).ToArray();
            isFolder = _dbEntry is FolderEntry;

            if (readableProperties.Contains(DirectoryInfoProperties.IsFileBasedFS) &&
                this.GetProperty(DirectoryInfoProperties.IsFileBasedFS).ValueAsBoolean)
            {
                var newList = new List<object>();
                newList.Add(DatabaseProperties.LastListTime);
                newList.AddRange(readableProperties);
                readableProperties = newList.ToArray();
            }

            var etor = readableProperties.Union(writableProperties).GetEnumerator();
            while (etor.MoveNext()) if (checkProperty(etor.Current)) yield return etor.Current;

            yield return DatabaseProperties.SupportedProperties;

            etor = DBEntryHelper.EntryPropertyGetterDic.Keys.GetEnumerator();
            while (etor.MoveNext()) if (checkProperty(etor.Current)) yield return etor.Current;

            if (isFolder)
                etor = DBEntryHelper.FolderEntryPropertyGetterDic.Keys.GetEnumerator();
            else etor = DBEntryHelper.FileEntryPropertyGetterDic.Keys.GetEnumerator();
            while (etor.MoveNext()) if (checkProperty(etor.Current)) yield return etor.Current;

            foreach (var ppair in _propertySupportDic)
                if (checkProperty(ppair.Key)) yield return ppair.Key;
        }

        public virtual PropertySupportOptions GetPropertySupportOptions(object property)
        {
            bool isFolder = false;
            object[] readableProperties = new object[] { },
                writableProperties = new object[] { };


            readableProperties = (from intString in _dbEntry.SupportedAttributes.Split(',')
                                  select COFEShared.PropertyDictionary.GetProperty(Int32.Parse(intString))).ToArray();
            writableProperties = (from intString in _dbEntry.WritableAttributes.Split(',')
                                  select COFEShared.PropertyDictionary.GetProperty(Int32.Parse(intString))).ToArray();
            isFolder = _dbEntry is FolderEntry;


            PropertySupportOptions retVal = PropertySupportOptions.None;

            if ((int)property == (int)DatabaseProperties.SupportedProperties)
                return PropertySupportOptions.CanRead;

            if (readableProperties.Contains(property))
                retVal |= PropertySupportOptions.CanRead;
            if (writableProperties.Contains(property))
                retVal |= PropertySupportOptions.CanWrite;

            if (DBEntryHelper.EntryPropertyGetterDic.Keys.Contains(property))
                retVal |= PropertySupportOptions.CanRead;
            if (DBEntryHelper.EntryPropertySetterDic.Keys.Contains(property))
                retVal |= PropertySupportOptions.CanWrite;

            if (isFolder)
            {
                if (DBEntryHelper.FolderEntryPropertyGetterDic.Keys.Contains(property))
                    retVal |= PropertySupportOptions.CanRead;
                if (DBEntryHelper.FolderEntryPropertySetterDic.Keys.Contains(property))
                    retVal |= PropertySupportOptions.CanWrite;
            }
            else
            {
                if (DBEntryHelper.FileEntryPropertyGetterDic.Keys.Contains(property))
                    retVal |= PropertySupportOptions.CanRead;
                if (DBEntryHelper.FileEntryPropertySetterDic.Keys.Contains(property))
                    retVal |= PropertySupportOptions.CanWrite;
            }

            if (_propertySupportDic.ContainsKey(property))
                retVal |= _propertySupportDic[property];

            return retVal;
        }




        public virtual PropertyPair GetProperty(object property)
        {
            try
            {
                object retVal = null;

                //Emit Offline FileAttribute if the volume is offline.
                if (FileSystemInfoProperties.FileAttributes.Equals(property))
                {
                    var dirLister = COFEShared.VolumeRegistrar.LookupRegisterVolume(
                      _dbEntry.PrimaryVolumeId).DirectoryLister as IPrimaryDirectoryLister;

                    if (dirLister.Status == DirectoryListerStatusMode.Offline)
                    {
                        retVal = FileAttributes.Offline |
                            (FileAttributes)COFEDBShared.
                            DBEntryPropertyManager.GetProperty(null, _dbEntry, property);
                        return new PropertyPair(property, retVal);
                    }
                }

                //Special implementation for resource list.
                var rla = EnumUtils<ResourceListAttribute>.FindAttribute(property);
                if (rla != null)
                {
                    var supportedProperties = GetSupportedProperty();
                    if (supportedProperties.Contains(property))
                        return new PropertyPair(property, 
                            new ResourceList(property, 
                            ResourceListAttributeExtension.GetFilteredProperties(property,
                            supportedProperties)));
                    else throw new NotSupportedException();
                }



                if (_propertySupportDic.ContainsKey(property))
                    retVal = _getProperty(property);

                if (retVal != null)
                    return new PropertyPair(property, retVal);

                if (isDatabaseProperty(property) ||
                            COFEDBShared.DBEntryPropertyManager.IsPropertyCached(null, _dbEntry, property))
                    retVal = COFEDBShared.DBEntryPropertyManager.GetProperty(null, _dbEntry, property);


                if (retVal != null)
                    return new PropertyPair(property, retVal);

                retVal = _getProperty(property);

                if (retVal != null)
                    return new PropertyPair(property, retVal);

                return null;
            }
            catch (System.IO.FileNotFoundException ex)
            {
                //CacheEntryWork.ScheduleWork(
                //COFEDBShared.DBEntryManager.RemoveEntry(_dbEntryId);
                throw ex; //re-throw
            }
            catch (Exception ex) //Other exception
            {
                Console.WriteLine(
                    "(Exception)DBPropertyProviderBase.GetProperty():" +
                    ex.Message);
                return null;
            }
        }


        public virtual void SetProperty(object property, object value)
        {

            COFEDBShared.DBOperations.UpdateDatabase(
                (context) => COFEDBShared.DBEntryManager.LookupEntry(context, _dbEntryId),
                (context, dbEntry) =>
                {
                    COFEDBShared.DBEntryPropertyManager.SetProperty(context, dbEntry, property, value);
                    _dbEntry.EntryProperties.Load();
                    _dbEntry = dbEntry;
                });


            if (!(property is DatabaseProperties)) //native entries property (e.g. FileEntry's LastListTime)
            {
                //Write to caching.
                _setProperty(property, value);
            }
        }

        #endregion

        #region private methods

        protected virtual bool isDatabaseProperty(object property)
        {
            return property is DatabaseProperties;
        }




        #endregion

        #region Data

        /// <summary>
        /// Property / Options.
        /// </summary>
        protected Dictionary<object, PropertySupportOptions> _propertySupportDic = new Dictionary<object, PropertySupportOptions>();
        protected Func<object, object> _getProperty = (prop) => { throw new NotSupportedException(); };
        protected Action<object, object> _setProperty = (prop, value) => { };


        protected string _fullParsePath;
        protected Guid _dbEntryId;
        protected Entry _dbEntry;

        #endregion


        #region Static Method

        public static bool EntryInDatabase(Guid entryGuid)
        {
            return COFEDBShared.DBOperations.ReadDatabase((context) =>
                context.Entries.Any(e => e.EntryId == entryGuid));
        }

        public static void DeleteEntryInDatabase(Guid entryGuid)
        {
            COFEDBShared.DBOperations.UpdateDatabase((context) =>
            {
                var entryToDelete = context.Entries.FirstOrDefault(e => e.EntryId == entryGuid);
                if (entryToDelete != null)
                {
                    context.Entries.DeleteObject(entryToDelete);
                    context.SaveChanges();
                }
            });
        }

        #endregion


        #region Public Properties



        #endregion



    }
}
